let GLOBAL = {'variable': {}};
let TreeUtil;
(function (jQuery) {
    let ganttView = null;
    let scrollLeft = 0;
    let opts;

    jQuery.fn.ganttView = function () {
        let args = Array.prototype.slice.call(arguments);
        if (args.length === 1 && typeof (args[0]) === "object") {
            ganttView = this;
            Gantt.build.call(ganttView, args[0]);
        } else if (args.length >= 1 && typeof (args[0] === "string")) {
            handleMethod.call(ganttView, args);
        }
    };
    $("#zoom_in").click(function () {
        opts.zoomLevel = 'h';
        $("#ganttChart").empty();
        $('div.headerTitle').css('height', '92px')
        Gantt.build();
    });

    $("#zoom_out").click(function () {
        opts.zoomLevel = 'd';
        $("#ganttChart").empty();
        $('div.headerTitle').css('height', '62px')
        Gantt.build();
    });


    function handleMethod(args) {

        if (args.length > 1) {
            if (args[0] === "getDatas" && typeof (args[1]) === "function") {
                let datas = [];
                opts.data.forEach(function (value) {
                    let data = {};
                    jQuery.extend(data, value);
                    data.series = value.series.filter(function (v) {
                        return !v._empty;
                    });
                    datas.push(data);
                })
                args[1](datas);
            }
        }
    }

    let Gantt = {
        defaultOpts: {
            showWeekends: true,
            cellWidth: 40,
            cellHeight: 50,
            vtHeaderWidth: 200,
            data: [],
            zoomLevels: ['d', 'h'],
            zoomLevel: 'd',
            dataUrl: null,
            behavior: {
                clickable: true,
                draggable: true,
                resizable: true
            }
        },
        build: function (options) {
            ganttView.children().remove();
            if (!opts) {
                opts = $.extend(true, Gantt.defaultOpts, options);
                DataUtils.formatData(opts.data);
            }
            Gantt.addCtrPanel()
            Gantt.addContent();
        },
        addCtrPanel: function () {
            $("#tableContent").empty();
            TreeUtil.renderTree(opts.data, "tableContent")
        },
        addContent: function () {
            Gantt.setWidthByZoom();
            //初始化  画布开始 结束日期
            let startEnd = DateUtils.getBoundaryDatesFromData();
            opts.start = new Date(startEnd[0].getTime() - DateUtils.dayToMills(4));
            //画布结束日期
            opts.end = new Date(startEnd[1].getTime() + DateUtils.dayToMills(10));

            ganttView.each(function () {
                let container = jQuery(this);
                let div = jQuery("<div>", {"class": "ganttview"});
                new Chart(div).render();
                container.append(div);
                new Behavior(container).apply();
            });

            //update
            JsPlumbUtil.drawWire();

            $("#slide_container").off("scroll").scroll(function () {
                JsPlumbUtil.drawWire();
                scrollLeft = $("#slide_container").scrollLeft()
            });
        },
        setWidthByZoom: function () {
            if (opts.zoomLevel === 'd') {
                opts.cellWidth = 80;
            } else {
                opts.cellWidth = 40;
            }
        }
    }

    let Chart = function (div) {

        function render() {
            addVtHeader(div, opts.data, opts.cellHeight, opts.vtHeaderWidth);
            let slideDiv = jQuery("<div>", {
                "class": "ganttview-slide-container",
                "id": 'slide_container',
                "css": {"position": 'relative'}
            });
            let dates = getDates(opts.start, opts.end);
            //添加头
            addHzHeader(slideDiv, dates);
            //beij1布局
            addGrid(slideDiv, opts, dates);

            addBlockContainers(slideDiv, opts.data, opts.cellHeight, 0);

            div.append(slideDiv);

            applyLastClass(div.parent());
        }


        let monthNames = ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"];

        function getDates(start, end) {
            let dates = [];
            dates[start.getFullYear()] = [];
            dates[start.getFullYear()][start.getMonth()] = [start];
            let last = start;
            while (last.getTime() < end.getTime()) {
                let next = DateUtils.addDays(new Date(last), 1);
                if (!dates[next.getFullYear()]) {
                    dates[next.getFullYear()] = [];
                }
                if (!dates[next.getFullYear()][next.getMonth()]) {
                    dates[next.getFullYear()][next.getMonth()] = [];
                }
                dates[next.getFullYear()][next.getMonth()].push(next);
                last = next;
            }
            return dates;
        }

        function addVtHeader(div, data, cellHeight, vtHeaderWidth) {
            let headerDiv = jQuery("<div>", {
                "class": "ganttview-vtheader",
                "css": {"width": vtHeaderWidth + "px"}
            });
            let headerTitleDiv = jQuery("<div>", {
                "class": "ganttview-vtheader-title",
                'id': 'header_title',
                "css": {"width": vtHeaderWidth + "px", "height": cellHeight * getHeadFactor() + 1 + "px"}
            });

            headerTitleDiv.append(jQuery("<div>", {
                "class": "ganttview-vtheader-title-name",
                "css": {"height": "100%", "line-height": cellHeight * getHeadFactor() + 1 + "px", "width": "80px"}
            }).append("名称"));

            headerDiv.append(headerTitleDiv);
        }


        function addHzHeader(div, dates) {
            let cellWidth = opts.cellWidth;
            let showWeekends = opts.showWeekends;
            let hoursOfDay = opts.hoursOfDay;
            let headerDiv = jQuery("<div>", {"class": "ganttview-hzheader"});
            let monthsDiv = jQuery("<div>", {"class": "ganttview-hzheader-months clearfix"});
            let daysDiv = jQuery("<div>", {"class": "ganttview-hzheader-days clearfix"});
            let hoursDiv = jQuery("<div>", {"class": "ganttview-hzheader-hours clearfix"});
            let totalW = 0;
            let factor = getFactor();
            for (let y in dates) {
                for (let m in dates[y]) {
                    let w = dates[y][m].length * cellWidth * factor;
                    totalW = totalW + w;
                    monthsDiv.append(jQuery("<div>", {
                        "class": "ganttview-hzheader-month",
                        "css": {"width": (w) + "px"}
                    }).append(y + "年" + monthNames[m]));
                    for (let d in dates[y][m]) {
                        let dayDiv = jQuery("<div>", {
                            "class": "ganttview-hzheader-day",
                            "css": {"width": (cellWidth * factor) + "px"}
                        });
                        dayDiv.append(dates[y][m][d].getDate());
                        if (DateUtils.isWeekend(dates[y][m][d]) && showWeekends) {
                            dayDiv.addClass("ganttview-weekend");
                        }
                        daysDiv.append(dayDiv);

                        if (isHourZoomLevel()) {
                            for (let hour in hoursOfDay) {
                                let hourDiv = jQuery("<div>", {
                                    "class": "ganttview-hzheader-day",
                                    "css": {"width": (cellWidth) + "px"}
                                });
                                hourDiv.append(hoursOfDay[hour]);
                                hoursDiv.append(hourDiv);
                            }
                        }
                    }
                }
            }
            monthsDiv.css("width", totalW + "px");
            daysDiv.css("width", totalW + "px");
            hoursDiv.css("width", totalW + "px");
            headerDiv.append(monthsDiv).append(daysDiv);
            if (isHourZoomLevel()) {
                headerDiv.append(hoursDiv);
            }
            div.append(headerDiv);
        }

        function addGrid(div, opts, dates) {

            let cellWidth = opts.cellWidth;
            let cellHeight = opts.cellHeight;
            let showWeekends = opts.showWeekends;

            let gridDiv = jQuery("<div>", {"class": "ganttview-grid", 'css': {'position': 'relative'}});
            let rowDiv = jQuery("<div>", {"class": "ganttview-grid-row clearfix"});
            //时间轴
            for (let y in dates) {
                for (let m in dates[y]) {
                    for (let d in dates[y][m]) {
                        for (let i = 0; i < GLOBAL.variable.factor; i++) {
                            let cellDiv = jQuery("<div>", {
                                "class": "ganttview-grid-row-cell",
                                "css": {"width": (cellWidth) + "px", "height": (cellHeight - 1) + "px"}
                            });
                            if (DateUtils.isWeekend(dates[y][m][d]) && showWeekends) {
                                cellDiv.addClass("ganttview-weekend");
                            }
                            rowDiv.append(cellDiv);
                        }
                    }
                }
            }
            //body
            let w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * cellWidth;
            rowDiv.css("width", w + "px");
            gridDiv.css("width", w + "px");
            addGridBody(opts.data, rowDiv, gridDiv);
            div.append(gridDiv);
        }


        function addGridBody(node, rowDiv, gridDiv) {
            for (let i = 0; i < node.length; i++) {
                let cloneRowDiv = rowDiv.clone();
                cloneRowDiv.droppable({
                    accept: '.task',
                    hoverClass: "gantt-drag-hover",
                });
                if (node[i].subs) {
                    addGridBody(node[i].subs, rowDiv, gridDiv);
                }
                gridDiv.append(cloneRowDiv);
            }

        }


        function addBlockContainers(div, data, cellHeight, pId) {
            if (data && data.length != 0) {
                let blocksDiv = jQuery("<div>", {"class": "ganttview-blocks"});
                for (i in data) {
                    let node = data[i];
                    let blockContainer = $("<div>", {
                        'calss': 'ganttview-block-container',
                        'css': {'height': cellHeight - 1 + 'px', 'padding-top': 10 + 'px', 'box-sizing': 'border-box'},

                    });

                    drawProgressBar(node, blockContainer, pId);
                    blocksDiv.append(blockContainer);
                    if (node.subs && !DataUtils.isFold(node)) {
                        addBlockContainers(blocksDiv, node.subs, cellHeight, node.id);
                    }
                }
                div.append(blocksDiv);
            }

        }


        function drawProgressBar(node, container, pId) {
            let cellWidth = opts.cellWidth;
            let start = opts.start;
            let cellHeight = opts.cellHeight;
            let hourW = GLOBAL.variable.factor;
            let startTime = node.start;
            let endTime = node.end;
            let daysBetween = DateUtils.daysBetween(startTime, endTime);
            let offset = DateUtils.daysBetween(start, startTime);

            let block = jQuery("<div>", {
                "class": "ganttview-block",
                "title": "loading",
                "css": {
                    "width": (daysBetween * hourW * cellWidth) + "px",
                    "height": cellHeight - 20 + "px",
                    "margin-left": (offset * hourW * cellWidth) + "px",
                    "line-height": cellHeight - 20 + "px"
                },
                id: node.id,
                pId: pId,
                s: node.start.getTime(),
                e: node.end.getTime()
            });


            addBlockData(block, node);
            if (!!node.options && node.options.color) {
                block.css("background-color", node.options.color);
            }
            let barName = $("<span class='nodeTitle'>" + node.name + "</span><span class='nodeTime'>" + formatToDayHour(daysBetween) + "</span>");

            block.append(jQuery("<div>", {
                "class": "ganttview-block-text",
                "id": node.id + "_t",
                "css": {"height": cellHeight - 10 + "px"}
            }).html(barName));

            $(container).append(block);
        }


        function getHeadFactor() {
            if (opts.zoomLevel === 'h') {
                return 3;
            }
            return 2;
        }

        function isHourZoomLevel() {
            return opts.zoomLevel === 'h';
        }

        /**
         * 系数
         */
        function getFactor() {
            let factor = 1;
            if (opts.zoomLevel === 'h') {
                factor = 4;
            }
            GLOBAL.variable.factor = factor;
            return factor;
        }


        function formatToDayHour(floatDay) {
            let day = Math.floor(floatDay);
            let text = '';
            if (day) {
                text = Math.floor(floatDay) + '天';
            }
            let hour = (floatDay % 1).toFixed(2) * 24;
            if (hour) {
                hour = Math.ceil(hour);
                if (hour === 24) {
                    hour--;
                }
                text += hour + "小时";
            }
            return text;
        }


        function addBlockData(block, node) {
            let options = {draggable: true, resizable: true};
            let blockData = {id: node.id, taskId: null, name: node.name};
            if (!!node.options) {
                jQuery.extend(options, node.options);
            }
            jQuery.extend(blockData, node);
            blockData.options = options;
            block.data("block-data", blockData);
        }

        function applyLastClass(div) {
            jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", div).addClass("last");
            jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", div).addClass("last");
            jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", div).addClass("last");
        }

        return {
            render: render
        };
    }

    let Behavior = function (div) {

        function apply() {

            if (opts.behavior.clickable) {
                bindBlockClick(div, opts.behavior.onClick);
            }

            if (opts.behavior.resizable) {
                bindBlockResize(div, opts.cellWidth, opts.start, opts.behavior.onResize);
            }

            if (opts.behavior.draggable) {
                bindBlockDrag(div, opts.cellWidth, opts.start, opts.behavior.onDrag);
            }
        }

        function bindBlockClick(div, callback) {
            jQuery("div.ganttview-block", div).on("click", function () {
                if (callback) {
                    callback(jQuery(this).data("block-data"));
                }
            });
        }

        /**
         *
         * @param div
         * @param cellWidth
         * @param startDate
         * @param callback
         */
        function bindBlockResize(div, cellWidth, startDate, callback) {
            $("div.ganttview-block", div).each(function () {
                if ($(this).data("block-data").options.resizable) {
                    $(this).resizable({
                        resize: function (event, ui) {
                            jsPlumb.revalidate(ui.helper)
                        },
                        handles: "w,e",
                        stop: function () {
                            let block = $(this);
                            let outerWidth = block.outerWidth();
                            block.width(outerWidth);
                            let offset = getOffset(block);
                            let position = getPosition(div, block, cellWidth, startDate, offset);
                            let changes = updateGanttOptsDatas(block, position.start, position.end);
                            if (changes) {
                                let pId = block.attr('pId');
                                updateP(pId, changes.start, changes.end);
                                let node = DataUtils.getNode(block.attr('id'));
                                if (node) {
                                    updateSubByResize(node, changes.start, changes.end);
                                }
                            }
                            Gantt.build();
                            if (callback) {
                                callback(block.data("block-data"));
                            }
                            $("#slide_container").scrollLeft(scrollLeft);
                        }
                    });
                }
            });
        }


        /**
         * 拖拽 整体
         * @param div
         * @param cellWidth
         * @param startDate
         * @param callback
         */
        function bindBlockDrag(div, cellWidth, startDate, callback) {
            jQuery("div.ganttview-block", div).each(function () {
                if (jQuery(this).data("block-data").options.draggable) {
                    jQuery(this).draggable({
                        axis: "x",
                        start: function () {
                            scrollLeft = $("#slide_container").scrollLeft()
                        },
                        drag: function (e) {
                            jsPlumb.revalidate($(this)); // Note that it will only repaint the dragged element
                        },
                        stop: function () {
                            let $block = $(this);
                            $block.width($block.outerWidth());
                            let offset = getOffset($block);
                            let position = getPosition(div, $block, cellWidth, startDate, offset);
                            let change = updateGanttOptsDatas($block, position.start, position.end);
                            if (change) {
                                let id = $block.attr('id');
                                let node = DataUtils.getNode(id);
                                if (node) {
                                    updateSubByDrag(node, change);
                                }
                                updateP($block.attr('pId'), change.start, change.end);
                            }

                            Gantt.build();
                            $("#slide_container").scrollLeft(scrollLeft)
                        }
                    });
                }
            });
        }

        function updateSubByDrag(pNode, change) {
            let subs = pNode.subs;
            if (subs) {
                for (let j = 0; j < subs.length; j++) {
                    let sub = subs[j];
                    sub.start = new Date(sub.start.getTime() + change.s);
                    sub.end = new Date(sub.end.getTime() + change.e);
                    if (sub.subs) {
                        updateSubByDrag(sub, change);
                    }
                }
            }
        }


        function updateSubByResize(pNode, start, end) {
            let subs = pNode.subs;
            if (subs) {
                for (let i = 0; i < subs.length; i++) {
                    let sub = subs[i];


                    let sO = sub.start.getTime();
                    let eO = sub.end.getTime();

                    if (sO < start.getTime()) {
                        sub.start = start;
                    }
                    if (eO > end.getTime()) {
                        sub.end = end;
                    }
                    if (sub.end.getTime() - sub.start.getTime() < DateUtils.hourToMills(1)) {
                        sub.start = new Date(sub.end.getTime() - DateUtils.hourToMills(1));
                    }

                    if (sub.subs) {
                        updateSubByResize(sub, start, end);
                    }
                }
            }
        }

        function updateP(pId, start, end) {
            if (pId && pId != 0) {
                let $pBar = $("#" + pId);
                updatePslopOverData($pBar, start, end);
                updateP($pBar.attr('pId'), start, end);
            }
        }


        function updatePslopOverData(bar, start, end) {
            let s = parseInt(bar.attr('s'));
            let e = parseInt(bar.attr('e'));
            let slopOver = false;
            if (s > start.getTime()) {
                slopOver = true;
                s = start.getTime();
            }
            if (e < end.getTime()) {
                slopOver = true;
                e = end.getTime();
            }
            if (slopOver) {
                updateData(bar.attr('id'), new Date(s), new Date(e), opts.data);
            }
        }


        function getOffset(block) {
            let container = jQuery("div.ganttview-slide-container", div);
            let scroll = container.scrollLeft();
            let offset = block.offset().left - container.offset().left - 1 + scroll;
            return offset;
        }

        function getPosition(div, block, cellWidth, startDate, offset) {
            // Set new start date
            let daysFromStart = offset / (cellWidth * GLOBAL.variable.factor);
            let newStart = DateUtils.addDays(new Date(startDate), daysFromStart);
            // Set new end date
            let width = block.outerWidth();
            let numberOfDays = width / (cellWidth * GLOBAL.variable.factor);
            let newEnd = DateUtils.addDays(new Date(newStart), numberOfDays);

            return {'start': newStart, 'end': newEnd};
        }


        function updateGanttOptsDatas(block, newStart, newEnd) {
            let id = block.attr('id');
            return updateData(id, newStart, newEnd, opts.data);
        }

        function updateData(id, newStart, newEnd, data) {
            let change;
            for (let i = 0; i < data.length; i++) {
                if (data[i].id == parseInt(id)) {
                    change = {
                        's': newStart.getTime() - new Date(data[i].start).getTime(),
                        'e': newEnd.getTime() - new Date(data[i].end).getTime(),
                        'start': newStart,
                        'end': newEnd
                    }
                    data[i].start = newStart;
                    data[i].end = newEnd;
                    return change;
                }
                if (data[i].subs) {
                    change = updateData(id, newStart, newEnd, data[i].subs);
                }
            }
            return change;
        }

        return {
            apply: apply
        };
    }

    let JsPlumbUtil = {
        isInit: false,
        common: {
            anchor: ['Left'],
            endpoint: "Blank",
            connector: ["Flowchart", {cornerRadius: 7, gap: 2}],
            maxConnections: -1,
            paintStyle: {stroke: '#778899', strokeWidth: 3},
            endpointStyle: {fill: 'blue', outlineStroke: 'blue', outlineWidth: 2},
            overlays: [['Arrow', {width: 12, length: 12, location: 1}]],
            isSource: true,  //创建可连接源(isSource,isTarget都为true就可以动态拖动连接了)
            isTarget: true
        },
        addWire: function (source, target) {
            let $source = $("#" + source);
            let $target = $("#" + target);
            if ($source.length != 0 && $target.length != 0) {

                jsPlumb.setContainer($("body"));
                jsPlumb.connect({
                    uuid: source + '_' + target,
                    source: source + '',
                    target: target + '',
                    newConnection: true
                }, JsPlumbUtil.common);
                JsPlumbUtil.isInit = true;
            }
        },
        addContainerWire: function (data, pId) {
            //add
            if (!data) {
                data = opts.data;
            }
            jsPlumb.ready(function () {
                for (let i = 0; i < data.length; i++) {
                    let node = data[i];
                    if (pId) {
                        JsPlumbUtil.addWire(pId, node.id)
                    }
                    if (node.subs) {
                        JsPlumbUtil.addContainerWire(node.subs, node.id);
                    }
                }
            });
        },
        drawWire: function () {
            if (JsPlumbUtil.isInit) {
                jsPlumb.reset();
            } else {
                jsPlumb.setContainer($("#slide_container"));
            }
            JsPlumbUtil.addContainerWire();
        }

    }

    TreeUtil = {
        renderTree: function (data, id) {
            for (let i = 0; i < data.length; i++) {
                if (data[i].subs) {
                    let subDisplay = '';
                    let pFoldFont = 'fa fa-plus-square-o';
                    if (!DataUtils.isFold(data[i])) {
                        subDisplay = 'style="display: block;"';
                        pFoldFont = 'fa fa-minus-square-o';
                    }
                    $("#" + id).append('<div><p class="tableItem">' +
                        '        <span><a onclick="TreeUtil.treeClick(this)" fold="' + data[i].fold + '" data-id="' + data[i].id + '" data-change="#subs' + data[i].id + '"><i class="' + pFoldFont + '"></i></a></span>' +
                        '        <span>' + data[i].name + '</span>' +
                        '        <span>' + DateUtils.stringAsStandard(data[i].start) + '</span>' +
                        '        <span>' + DateUtils.stringAsStandard(data[i].end) + '</span>' +
                        '    </p></div><div class="subs" ' + subDisplay + ' id="subs' + data[i].id + '"></div>');
                    TreeUtil.renderTree(data[i].subs, "subs" + data[i].id);
                } else {
                    $("#" + id).append('<div><p class="tableItem">' +
                        '       <span></span>' +
                        '       <span>' + data[i].name + '</span>' +
                        '       <span>' + DateUtils.stringAsStandard(data[i].start) + '</span>' +
                        '       <span>' + DateUtils.stringAsStandard(data[i].end) + '</span>' +
                        '   </p></div>')
                }
            }
        },
        treeClick: function (el) {
            let dataId = $(el).attr('data-id');
            TreeUtil.updateFold(dataId);
            Gantt.build();
        },
        updateFold: function (id) {
            TreeUtil.setFold(id, opts.data);
        },
        setFold: function (id, data) {
            for (let i = 0; i < data.length; i++) {
                if (data[i].id == parseInt(id)) {
                    let value = $("a[data-id='" + id + "']").attr('fold');
                    value = value === 'true'
                    data[i].fold = !value;
                    return;
                }
                if (data[i].subs) {
                    TreeUtil.setFold(id, data[i].subs);
                }
            }
        }
    }

    let DataUtils = {
        formatData: function (data) {
            for (let dataKey in data) {
                data[dataKey].start = DateUtils.formatTimePrecisionHour(data[dataKey].start);
                data[dataKey].end = DateUtils.formatTimePrecisionHour(data[dataKey].end);
                if (data[dataKey].subs) {
                    DataUtils.formatData(data[dataKey].subs);
                }
            }
        },
        isFold: function (node) {
            if (!node.fold) {
                return false;
            }
            return node.fold;
        },
        getNode: function (id, data) {
            if (!data) {
                data = opts.data;
            }
            let node;
            for (let i = 0; i < data.length; i++) {
                if (data[i].id === parseInt(id)) {
                    return data[i];
                }
                if (data[i].subs) {
                    node = DataUtils.getNode(id, data[i].subs);
                    if (node) {
                        return node;
                    }
                }
            }
        }
    }

    let DateUtils = {
        stringAsStandard: function (date) { //author: meizz
            date = DateUtils.formatTimePrecisionHour(date);
            let o = {
                "M+": date.getMonth() + 1,                 //月份
                "d+": date.getDate(),                    //日
                "h+": date.getHours(),                   //小时
                "m+": date.getMinutes(),                 //分
                "s+": date.getSeconds(),                 //秒
                "q+": Math.floor((date.getMonth() + 3) / 3), //季度
                "S": date.getMilliseconds()             //毫秒
            };
            let fmt = 'yyyy-MM-dd hh:mm:ss';
            if (/(y+)/.test(fmt))
                fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
            for (let k in o)
                if (new RegExp("(" + k + ")").test(fmt))
                    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
            return fmt;
        },
        formatTimePrecisionHour: function (date) {
            date = new Date(date);
            date.setHours(date.getHours() + Math.round(date.getMinutes() / 60), 0, 0);
            return date;
        },
        addDays: function (date, number) {
            let adjustDate = new Date(date.getTime() + 24 * 60 * 60 * 1000 * number);
            return adjustDate;
        },
        dayToMills: function (day) {
            return 24 * 60 * 60 * 1000 * day;
        },
        hourToMills: function (hour) {
            return 60 * 60 * 1000 * hour;
        }
        ,
        daysBetween: function (start, end) {
            if (!start || !end) {
                return 0;
            }
            if (new Date(start).getFullYear() === 1901 || new Date(end).getFullYear() === 8099) {
                return 0;
            }
            let of = (end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000);
            return of;
        },

        isWeekend: function (date) {
            return date.getDay() % 6 == 0;
        },

        /**
         * 时间轴
         * @param data
         * @param minDays
         * @returns {*[]}
         */
        getBoundaryDatesFromData: function () {
            let data = opts.data;
            let minDays = Math.floor(((ganttView.outerWidth() - opts.vtHeaderWidth) / opts.cellWidth) + 15);
            let minStart = DateUtils.addDays(new Date(), -15);
            let maxEnd = new Date(0);
            for (let i = 0; i < data.length; i++) {
                let node = data[i];
                if (!node.start || !node.end) {
                    continue;
                }
                // series.start = new Date()
                let start = new Date(node.start.getTime());
                let end = new Date(node.end);
                if (i == 0) {
                    minStart = new Date(start);
                    maxEnd = new Date(end);
                }
                if (minStart.getTime() > start.getTime()) {
                    minStart = new Date(start);
                }
                if (maxEnd.getTime() < end.getTime()) {
                    maxEnd = new Date(end);
                }
            }
            if (DateUtils.daysBetween(minStart, maxEnd) < minDays) {
                maxEnd = DateUtils.addDays(minStart, minDays);
            }
            minStart.setHours(0, 0, 0, 0)
            return [minStart, maxEnd];
        }
    };
})(jQuery);
